#include "vfx_water.h"
#include "graphics/graphics.h"
#include "graphics/image.h"
#include "graphics/surface.h"
#include "graphics/graphics_util.h"
#include "dd/dd_man.h"

#include "util/timer.h"
#include "script/scripthelper.h"
#include "math/intersection.h"

#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    


#include <algorithm>
#include <cassert>
using namespace vfx;

VFXWater::VFXWater(const math::Vec2& Offset, const math::Vec2& Size) :
	m_Width(0), 
	m_Height(0), 
	m_GridA(0),
	m_TempA(0),
	m_GroundA(0),
	m_Simulate(0),
	m_Offset(Offset),
	m_Size(Size),
	m_VertexA(0),
	m_IndexA(0),
	m_pUnderwater(0),
	m_UnderwaterC(0),
	m_BaseColor(128),
	m_TexW(0), m_TexH(0),
	m_IndexC(0)
{
}



VFXWater::~VFXWater()
{
	_LOG(MSG_COMMENT, "   Erasing grid");
	UTIL_SAFE_DELETEA(m_GridA);
	_LOG(MSG_COMMENT, "   Erasing last grid");
	UTIL_SAFE_DELETEA(m_TempA);
	_LOG(MSG_COMMENT, "   Erasing Ground-definition");
	UTIL_SAFE_DELETEA(m_GroundA);
	_LOG(MSG_COMMENT, "   Erasing Index Array");
	UTIL_SAFE_DELETEA(m_IndexA);
	_LOG(MSG_COMMENT, "   Erasing Vertex Array");
	//_LOG(MSG_COMMENT, "   Vertex Array Pointer " << (unsigned int)m_VertexA);
	UTIL_SAFE_DELETEA(m_VertexA);
	_LOG(MSG_COMMENT, "   Erasing Texture Cache");
	//_LOG(MSG_COMMENT, "   Texture Cache Pointer " << (unsigned int)m_pUnderwater);
	UTIL_SAFE_DELETE(m_pUnderwater);
}

void			VFXWater::ClearGround()
{
//	assert(m_validateAddress(m_GridA));
	for (int x=0; x<m_Width; x++)
	for (int y=0; y<m_Height; y++)
	{
		m_GroundA[x + (y*m_Width)] = false;
	}
//	assert(m_validateAddress(m_GridA));
}
void			VFXWater::AddGroundSphere(const math::Vec2& Pos, float Rad, bool Val)
{
//	assert(m_validateAddress(m_GridA));
	int minx, miny, maxx, maxy;
	math::Vec2	Delta(Rad, Rad);
	GetExtents(minx, miny, maxx, maxy, Pos-Delta, Pos+Delta);
	for (int x=minx; x<=maxx; x++)
	for (int y=miny; y<=maxy; y++)
	{
		math::Vec2	P = m_Offset + math::Vec2(x*m_BlockSize.x, y*m_BlockSize.y);
		if ( (Pos-P).Magnitude() < Rad)
			m_GroundA[GetIndex(x, y)] = Val;
	}
}


void	VFXWater::GetExtents(int& minx, int& miny, int& maxx, int& maxy, const math::Vec2& Offset)
{
	minx = int(((m_Offset.x-0.05f)+Offset.x) / m_BlockSize.x);
	maxx = int(((m_Offset.x+1.1f)+Offset.x) / m_BlockSize.x);

	miny = int(((m_Offset.y-0.05f)+Offset.y) / m_BlockSize.y);
	maxy = int(((m_Offset.y+1.1f)+Offset.y) / m_BlockSize.y);
	UTIL_CLAMP(minx, 1, m_Width-2);
	UTIL_CLAMP(maxx, 1, m_Width-2);
	UTIL_CLAMP(miny, 1, m_Height-2);
	UTIL_CLAMP(maxy, 1, m_Height-2);
}

void	VFXWater::GetExtents(int& minx, int& miny, int& maxx, int& maxy, const math::Vec2& From, const math::Vec2& To)
{
	minx = int((m_Offset.x+From.x) / m_BlockSize.x);
	maxx = int((m_Offset.x+To.x) / m_BlockSize.x);

	miny = int((m_Offset.y+From.y) / m_BlockSize.y);
	maxy = int((m_Offset.y+To.y) / m_BlockSize.y);
		
	UTIL_CLAMP(minx, 1, m_Width-2);
	UTIL_CLAMP(maxx, 1, m_Width-2);
	UTIL_CLAMP(miny, 1, m_Height-2);
	UTIL_CLAMP(maxy, 1, m_Height-2);
}

void	VFXWater::SetBackground(const std::string& File)
{
	m_BaseTexture.Create(File);
	if (m_BaseTexture.GetImage() && m_BaseTexture.GetImage()->GetSurface())
	{
		//m_pBaseTexturePixels = m_pBaseTexture->GetSurface()->GetPixels();
		m_UVScale.x = m_Size.x / (m_BaseTexture.GetImage()->GetSurface()->GetWidth() * GraphicsUtil::InvW);
		m_UVScale.y = m_Size.y / (m_BaseTexture.GetImage()->GetSurface()->GetHeight() * GraphicsUtil::InvH);
	}
}

void	VFXWater::Create(int Width, int Height, const std::string& Texture)
{
	m_Width = Width;
	m_Height = Height;

	m_BlockSize.x = m_Size.x / float(m_Width);
	m_BlockSize.y = m_Size.y / float(m_Height);


	// height array
	m_GridA = new int[m_Width*m_Height];
	m_TempA= new int[m_Width*m_Height];
	//_LOG(MSG_COMMENT, "    Arrays created ("<<m_Width<<"x"<<m_Height<<") : " << (unsigned int)m_GridA << " & " << (unsigned int)m_TempA);

	m_GroundA = new bool[m_Width*m_Height];
	//_LOG(MSG_COMMENT, "    Ground created ("<<m_Width<<"x"<<m_Height<<") : " << (unsigned int)m_GroundA);
	for (int i=0; i<m_Width*m_Height; i++)
	{
		m_GridA[i] = m_TempA[i] = 0;
	}
	_LOG(MSG_COMMENT, "    Grid reset");

	ClearGround();
	_LOG(MSG_COMMENT, "    Ground Cleared");

	// create Water texture
	SetBackground(Texture);
	_LOG(MSG_COMMENT, "    Background texture " << Texture );

	// create underwater source
	m_TexW = 1024;
	m_TexH = 1024;
	m_pUnderwater = GetGraphics()->CreateCache(m_TexW, m_TexH, 16);
	//_LOG(MSG_COMMENT, "    Cache created : 1024x1024x16 : " << (unsigned int)m_pUnderwater);
	m_pUnderwater->ClearCache(0x00FF00FF);
	_LOG(MSG_COMMENT, "    cache Cleared");

	// create and fill the vertex buffer:
	int IndexC = 0;
	int minx, miny, maxx, maxy;
	GetExtents(minx, miny, maxx, maxy, math::Vec2(0,0));
	m_RenderW = (maxx-minx) + 1;
	m_RenderH = (maxy-miny) + 1;
	_LOG(MSG_COMMENT, "    Extents calculated : " << m_RenderW << "x" << m_RenderH);

	// this holds the max size.
	m_VertexA = new Graphics::Vertex2D[m_RenderW*m_RenderH];
	//_LOG(MSG_COMMENT, "    Vertex Buffer created : " << (unsigned int)m_VertexA);


	m_IndexA = new unsigned short[(m_RenderW-1)*(m_RenderH-1)*6];
	//_LOG(MSG_COMMENT, "    Index buffer created : " << (unsigned int)m_IndexA);

	BuildIndices();

	_LOG(MSG_COMMENT, "    Index buffer setup");
}

void	VFXWater::BuildIndices()
{
	m_IndexC = 0;
	for (int x=0; x<m_RenderW-1; x++)
	for (int y=0; y<m_RenderH-1; y++)
	{
		// add only if one of vertices is not water?
		//if (IsGround(x, y)) continue;	
		m_IndexA[m_IndexC++] = x + (y*m_RenderW);
		m_IndexA[m_IndexC++] = x + 1 + (y*m_RenderW);
		m_IndexA[m_IndexC++] = x + 1 + (y*m_RenderW) + m_RenderW;

		m_IndexA[m_IndexC++] = x + (y*m_RenderW);
		m_IndexA[m_IndexC++] = x + 1 + (y*m_RenderW) + m_RenderW;
		m_IndexA[m_IndexC++] = x + (y*m_RenderW) + m_RenderW;
	}

}

int		VFXWater::GetIndex(int x, int y)
{
	UTIL_CLAMP(x, 0, m_Width-1);
	UTIL_CLAMP(y, 0, m_Height-1);
	return x + (y*m_Width);
}

void	VFXWater::SetImpulse(int	x, int y, int Height)
{
	if (!IsGround(x, y))
	{
		UTIL_CLAMP(Height, 0, 75);
		m_GridA[GetIndex(x, y)] = Height;
	}
}


void	VFXWater::AddWave(const math::Vec2& Pos, float Depth)
{
	// is it inside?
	int x = int((Pos.x - m_Offset.x) / m_BlockSize.x);
	int y = int((Pos.y - m_Offset.y) / m_BlockSize.y);
	if (x >= 0 && y >= 0 && x < m_Width && y < m_Height)
	{
		SetImpulse(x, y, (int)Depth);
	}
}

void	VFXWater::AddWaveArea(const math::Vec2& From, const math::Vec2& To, float Depth)
{
	int fx = int((From.x - m_Offset.x) / m_BlockSize.x);
	int fy = int((From.y - m_Offset.y) / m_BlockSize.y);

	int tx = int((To.x - m_Offset.x) / m_BlockSize.x);
	int ty = int((To.y - m_Offset.y) / m_BlockSize.y);

	UTIL_CLAMP(fx, 0, m_Width-1);
	UTIL_CLAMP(tx, 0, m_Width-1);

	UTIL_CLAMP(ty, 0, m_Height-1);
	UTIL_CLAMP(ty, 0, m_Height-1);

	for (int x=fx; x<=tx; x++)
	for (int y=fy; y<=ty; y++)
		SetImpulse(x, y, (int)Depth);
}


void	VFXWater::Tick(float dt, const math::Vec2& Offset)
{
	m_BaseTexture.Tick(dt);
	RenderUnderWater(Offset, 1);
	m_Simulate += dt;	// only update 30 fps
	if (m_Simulate > 1.0f/30.0f)
	{
		int minx, miny, maxx, maxy;
		GetExtents(minx, miny, maxx, maxy, Offset);
		// only update an area slightly larger than the screen
		for (int y=miny; y<maxy; y++)
		for (int x=minx; x<maxx; x++)
		{
			int	i = GetIndex(x, y);
			if (!m_GroundA[i])
			{
				m_TempA[i] = (
    							((m_GridA[i-1]+
								 m_GridA[i+1]+
								 m_GridA[i - m_Width]+
								 m_GridA[i + m_Width])  >>1) )	-m_TempA[i];

				m_TempA[i] -= (m_TempA[i] >> 6);
			}
		}
		m_Simulate = 0.0f;
		// swap
		int* temp = m_TempA;
		m_TempA = m_GridA;
		m_GridA = temp;
	}
}

bool	VFXWater::IsGround(int x, int y) const
{
	assert(x >= 0 && x < m_Width && y >=0 && y < m_Height);
	return m_GroundA[x + (y*m_Width)];
}


void		VFXWater::RenderDualLayer(const math::Vec2& Offset, int minx, int miny, int maxx, int maxy)
{
	float	Width = GetGraphics()->GetScreenWidth();
	float	Height = GetGraphics()->GetScreenHeight();

	math::Vec2 Size(m_Size.x / float(m_Width), m_Size.y / float(m_Height));

	int VertC = 0;
	int		tox = minx+m_RenderW;
	int		toy = miny+m_RenderH;
	UTIL_CLAMP(tox, 0, m_Width-2);
	UTIL_CLAMP(toy, 0, m_Height-2);

	VertC = 0;
	float	tvmul = 1;
	//CHIT: Fucking opengl uses upside down textures
	if(GetGraphics()->GetGraphicsClass()  == Graphics::GRAPHICS_CLASS_OGL)	
		tvmul = -1;
	float mx = GraphicsUtil::WIDTH/float(m_TexW);
	float my = GraphicsUtil::HEIGHT/float(m_TexH);
	for (int y=miny; y<toy; y++)
	for (int x=minx; x<tox; x++)
	{
		int	i = GetIndex(x, y);
		int	Xoffset = m_GridA[i-1] - m_GridA[i+1];
		int	Yoffset = m_GridA[i-m_Width] - m_GridA[i+m_Width];
		float DeltaU = (float(Xoffset)*0.1f) / float(m_Width);
		float DeltaV = (float(Yoffset)*0.1f) / float(m_Height);

		float xp = m_Offset.x + (x*Size.x) - Offset.x;
		float yp = m_Offset.y + (y*Size.y) - Offset.y;
		float u =  (xp*mx) + DeltaU;
		float v =  (yp*my) + DeltaV;
		UTIL_CLAMP(u, 1/float(m_TexW), (m_TexW-1)/float(m_TexW));
		UTIL_CLAMP(v, 1/float(m_TexH), (m_TexH-1)/float(m_TexH));
		if (u < 0) u = 0;
		if (v < 0) v = 0;

		int Blue = int(m_BaseColor + Xoffset);
		UTIL_CLAMP(Blue, 0, 255);
		COLOR c = ColorRGBA(Blue, Blue, Blue, 255);
		if (m_GroundA[i]) 
			c = ColorRGBA(0,0,0,0);

		m_VertexA[VertC++].Fill(xp*Width, yp*Height, c, u, v*tvmul);

	}
	if (VertC > 0 && m_pUnderwater)
	{
		m_pUnderwater->SetTextureStage(0);
		GetGraphics()->DrawIndexVertex2D(m_VertexA, VertC, m_IndexA, m_IndexC);
	}
}

void	VFXWater::Render(const math::Vec2& Offset, float Scale)
{
	//if (!m_BaseTexture.IsValid()) return;
	int minx, miny, maxx, maxy;
	GetExtents(minx, miny, maxx, maxy, Offset);
	//int IndexC= (m_RenderW-1)*(m_RenderH-1)*6;
	RenderDualLayer(Offset, minx, miny, maxx, maxy);
	//RenderDebug(Offset, Scale);
}

void	VFXWater::RenderUnderWater(const math::Vec2& Offset, float Scale)
{
}

/*void			VFXWater::RenderUnderWater(const math::Vec2& Offset, HordeObject**			ObjectSetA, int Count)
{
	if (!m_pUnderwater) return;

	for (int i=0; i<Count; i++)
	{
		if (ObjectSetA[i]->RenderUnderWater())			ObjectSetA[i]->RenderWater();
		if (ObjectSetA[i]->HasReflection())			ObjectSetA[i]->RenderReflection();
	}
}*/

bool	VFXWater::StartCache(const math::Vec2& Offset, float Scale)
{
	if (!m_pUnderwater) return false;
	if (!m_pUnderwater->BeginCache()) return false;
	m_pUnderwater->ClearCache(0x00000000);
	if (m_BaseTexture.GetImage())
	{
		m_BaseTexture.GetImage()->SetTextureStage(0);
		Graphics::Vertex2D		VertexA[6];
		math::Vec2	Rep, Start;
		Rep.x = GraphicsUtil::WIDTH / (float)m_BaseTexture.GetImage()->GetWidth();
		Rep.y = GraphicsUtil::HEIGHT / (float)m_BaseTexture.GetImage()->GetHeight();
		Start.x = Rep.x * Offset.x;
		Start.y = Rep.y * Offset.y;
		GraphicsUtil::FillQuad(math::Vec2(-0.0,-0.0f), math::Vec2(1.0,1.0), Start, -(Start+(Rep*1.0f)), 0xFFFFFFFF, VertexA);
		GetGraphics()->SetRenderState(Graphics::RS_ALPHA, false);
		GetGraphics()->DrawVertex2D(VertexA, 6);
		GetGraphics()->SetRenderState(Graphics::RS_ALPHA, true);
	}
	return true;
}


void	VFXWater::FinishCache()
{
	if (!m_pUnderwater) return;
	m_pUnderwater->EndCache();
}

void	VFXWater::RenderDebug(const math::Vec2& Offset, float Scale)
{
	dd::Manager& DD = dd::Manager::Instance();
	math::Vec2	P;
	math::Vec2 Size = m_BlockSize;//(m_Size.x / float(m_Width), m_Size.y / float(m_Height));

	// draw water/land
	for (int x=0; x<m_RenderW-1; x++)
	for (int y=0; y<m_RenderH-1; y++)
	{
		// add only if one of vertices is not water
		COLOR		c = 0xFFFFFFFF;
		if (IsGround(x, y)) c = ColorRGBA(0, 0, 255);
		int i = GetIndex(x, y);
	
		P.x = m_Offset.x + (x*Size.x) - Offset.x;
		P.y = m_Offset.y + (y*Size.y) - Offset.y;
		DD.AddBox2D(P, P+Size, c);
	}
}

